Skip to content

Productize automatic execution cadence in Goal settings - #4933

Merged
huangruiteng merged 8 commits into
mainfrom
codex/automation-cadence-settings
Sep 24, 2026
Merged

huangruiteng merged 8 commits into
mainfrom
codex/automation-cadence-settings

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

Problem and behavior

The quota-owned automatic-execution minimum interval from #4921 had no Goal settings editor, so an operator could not inspect inheritance or safely change a Goal, Agent, or automation floor from the product. Goal-specific settings also did not identify which Goal was being edited.

This adds an Automatic execution interval settings panel with effective-floor provenance, scoped preview, revision-checked apply, and readback. The shared settings heading now names the active Goal on Goal-specific tabs and clears that label on machine-wide tabs. The loopback API delegates policy decisions and writes to the existing quota owner; it does not create a second scheduler policy. The RFC checkpoint is updated to reflect the delivered UI and remaining host-admission work.

The panel explicitly states that this is a scheduler recommendation: it does not mutate existing Codex App automation schedules, and App timer-to-pre-model-hook admission remains unqualified. Managed Turn admission is tracked separately in #4929.

Validation

  • 83 focused Python tests covering real loopback HTTP, quota file-store readback, stale revisions, scope inheritance, and explicit reduction intent.
  • Dashboard TypeScript check and packaged chat build passed.
  • Development and packaged browser scenarios passed for preview/apply/readback, Goal identity across tabs, Agent inheritance, and desktop/mobile layout.
  • Ruff lint/format and risk-based premerge passed; premerge selected 19 checks with no failures or manual holds. Public/private boundary scan passed.

Future-facing boundary pass: reused the existing Goal settings navigation and quota authority, removed duplicate readback metrics, and left App hook work outside this PR.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes conclusion (author-owned PR; GitHub blocks formal self-review)

English verdict: REQUEST_CHANGES - head c350b60; local API, build and packaged UI checks passed, but the conflict-status classifier needs a typed source.

动机

把已有的 quota-owned 自动执行间隔策略放进 Goal 设置,让所有者能按 Goal / Agent / automation 范围检查、预览、保存和读回。此 PR 不声称能修改 Codex App 定时器,也不声称已具备启动模型前拦截。

改动思路

前端调用本地 chat 配置 API;Python 只校验请求和做回读,策略计算与持久化仍由 TypeScript 的 quota.automation_cadence.manage 负责。基线相同 loopback GET 返回 404;此 head 返回 200,预览不写入,执行后按修订号及来源回读。

具体改动

  • 设置页新增作用域、直接/继承下限、所有者原因、降额确认与预览-应用交互;界面明确“排程建议,App hook 尚未验证”。打包桌面/移动端场景通过。
  • 真实 loopback→TS store 测试通过(1 项),覆盖预览、应用、继承、旧修订拒绝和降额确认;打包构建/类型检查、Ruff、diff check 均通过。
  • [P2,需修复] loopx/chat_automation_cadence_api.py:223-231 用异常文案是否包含 revision conflict 或 stale 来决定 409 和 automation_cadence_conflict。TS 当前对修订冲突复用通用 automation_cadence_invalid 拒绝码,因此文案一变,冲突就会被误报为 400/invalid request。最小反例:给处理器一个 EffectRuntimeConflict("policy advanced", diagnostic_code="configuration_revision_conflict"),实测得到 400。请在 TS owner 发出独立的类型/诊断码,Python 按类型/码映射,并用不含现有关键词的冲突文案加回归。不要把错误文本当状态机。

对主干的风险

上述误分类会破坏调用者区分“刷新后重试”和“输入错误”的稳定 API 语义。App 定时触发到 pre-model hook 的资格仍未证明,PR 的边界文案对此诚实;浏览器场景 mock HTTP,但真实 loopback 测试覆盖策略写入。未按本 Goal 的 wait_for_ci=false 配置轮询远端 CI。

我的整体评价

方向和模块归属正确,交互/回读切片可独立验证;请先修 typed conflict 及负例,再在新 head 重跑 exact-head review。此变更涉及运行时与前端,按仓库规则由维护者合并。

…ge text

The loopback settings API decided 409/automation_cadence_conflict by looking for
'revision conflict' or 'stale' inside the error text, while the quota owner
raised a generic request_rejected for a stale expected_revision. Rewording that
message would silently turn a refresh-and-retry conflict into a 400 invalid
request.

The owner now raises EffectRuntimeConflictError with the stable diagnostic code
automation_cadence_revision_conflict, and the API maps failures by type: typed
conflicts (including a superseded preview digest) stay 409, rejected input stays
400.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
The prior regression passed only because the conflict message contained the
keyword the API matched. The new case raises a typed conflict whose text carries
no legacy keyword, and the end-to-end loopback test asserts a superseded preview
digest is still 409 with the conflict error code.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Take main's generated chat bundle as the provisional side of a rename/rename
conflict and keep both RFC narratives: main's two-phase managed-start recovery
text plus this branch's M3 settings companion. The bundle is rebuilt from the
merged sources in the following commit.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
The chat bundle conflicted as a rename/rename of generated assets. Rebuilding
with vite.config.chat.config.ts from the merged dashboard sources restores a
bundle that carries this branch's cadence settings page, keeps the bounded
two-generation retention manifest, and prunes the superseded assets.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

动机

上一轮评审在 c350b60b64689438ddf51b3ac40e5e929d7b9093 上请求修改:设置页 loopback API 用异常文案是否包含 revision conflict 或 stale 来决定 409/automation_cadence_conflict,而 quota owner 对过期 expected_revision 抛出的却是通用 request_rejected。这意味着只要 TS 侧改写文案,调用者就会把"刷新后重试"的冲突误判为"输入非法",语义由字符串决定而不是由类型决定。

本 head bbc3fd4de0b3b0d9e00e64d5434722936e68604e 修复了该点。PR 本身的目标是把已有的 quota-owned 自动执行间隔策略产品化到 Goal 设置(M3 设置页阶段成果),让所有者按 Goal/Agent/automation 作用域检查、预览、保存并读回,同时不声称修改 Codex App 定时器或已具备启动前拦截。

改动思路

策略权威不变:TypeScript 的 quota.automation_cadence.manage 负责策略校验与持久化,Python 只做请求校验和回读,前端通过本地 chat 配置 API 访问,并用预览摘要(preview_revision)锁定"我预览的就是我要应用的修订号"。

本次修复把这条边界上的失败分类改成类型驱动:owner 在 expected_revision 与持久修订不一致时抛出带稳定诊断码的 typed conflict(automation_cadence_revision_conflict);Python 按异常类型映射——typed conflict(含被取代的预览摘要)→ 409 + automation_cadence_conflict,被拒绝的输入 → 400 + invalid_automation_cadence_request。错误文案不再参与状态判定,改写文案不会改变 API 契约。

具体改动

本 head 相对上一轮的改动集中在后端分类与回归:loopx/chat_automation_cadence_api.py 新增 SupersededCadencePreview 与 _cadence_failure_status(按类型映射状态与错误码);loopx/control_plane/quota/automation_cadence.ts 新增 typed conflict 抛出口并用于修订冲突;tests/test_chat_automation_cadence_api.py 增加"文案不含旧关键词仍为 409"的回归并断言过期预览摘要为 409;tests/control_plane_ts/automation_cadence.test.ts 断言冲突的 kind/code。PR 其余部分(设置页交互、i18n、样式、chat 数据访问、重打包的 chat bundle、两份 RFC)保持不变。

本 head 同时合入最新 main:冲突集中在生成的 chat bundle(rename/rename)与 RFC 附录叙述,解决方式是采用 main 的一代产物作为过渡、保留双方 RFC 叙述(main 的两阶段启动恢复 + 本 PR 的 M3 设置页成果),随后用 vite.config.chat.config.ts 从合并后的源码重新构建 bundle。

关键代码讲解

  • _cadence_failure_status 与 SupersededCadencePreview(loopx/chat_automation_cadence_api.py):把 HTTP 状态与错误码绑定到失败类型;预览摘要不再匹配提交字段时也走同一条 typed conflict 分支,保持原有 409 语义。
  • conflict() 与 manageAutomationCadence 的修订检查(loopx/control_plane/quota/automation_cadence.ts):过期配置意图现在以 kind=conflict、code=automation_cadence_revision_conflict 越界,而不是 request_rejected。
  • automation-cadence-settings.tsx(前端,本次未改):预览→应用→读回的交互仍依赖同一 API,因此分类修复直接改善"刷新后重试"的提示语义。
  • 重新生成的 chat bundle(loopx/web/chat):经 vite.config.chat.config.ts 从合并源码构建,asset-retention.json 仍限制在两代之内并清理了被取代的产物。
  • 回归用例:test_chat_cadence_conflict_status_ignores_message_wording 在修复前会得到 400(已实测:assert 400 == 409),修复后为 409;端到端用例新增过期预览摘要 409 断言。

对主干的风险

最强反例就是上一轮给出的:把冲突文案改成不含旧关键词的措辞,旧实现立刻把冲突降级为 400,调用者据此放弃可恢复的重试。该反例现在有专门回归覆盖,并且在修复前的代码上确实失败,说明测试真的守住了这条语义。

边界与残留:App 定时触发到启动前 hook 的准入仍未验收,设置页只是候选成果而非推广;本次修复未改动前端交互,但合并后从源码重建了 chat bundle 并通过 dashboard-pwa-bundle-smoke.py 的 PWA 与资产保留契约;Lark/CLI 等待反馈一致性仍按 RFC 未完成项推进。

验证矩阵(本 head):Python chat 设置 API、Goal/machine 配置、automation cadence 与 Turn executor 共 95 用例通过;TS cadence 用例 7 项通过(含 typed kind/code 断言);npm run typecheck:control-plane、配置范围内 Ruff、python -m mypy(23 模块)、examples/docs-governance-smoke.py 与 examples/dashboard-pwa-bundle-smoke.py 通过;vite.config.chat.config.ts 重建 chat bundle 成功。本机未运行 Playwright 浏览器 smoke(需要浏览器依赖),merge 前的远端 CI 在该 PR 的上一 head 上 dashboard-acceptance、Windows/macOS desktop 与全部测试分片均通过;按本 Goal 的 wait_for_ci=false 与本轮维护者要求,未抓取或等待远端 CI。

语义与 CI 对齐

typed conflict 使用的是既有 effect-runtime 词汇(kind=conflict + 稳定诊断码),没有新增持久格式或公共字段;变化的只是设置 API 的失败分类来源。两份 RFC 追加了两阶段启动恢复与 M3 设置页边界,中英文同步。

我的整体评价

APPROVE:上一轮的唯一阻塞项已被实现为类型驱动的失败分类,并配有会在旧代码上失败、在新代码上通过的回归;设置页交互、回读与资产保留边界保持原样,chat bundle 从合并源码重建而非手改产物。范围仍与已复现问题相称,未引入新的 provider 或默认行为变更;剩余 App timer 路径与 Lark/CLI 等待一致性仍按 RFC 条件推进。

English verdict: APPROVE - exact head bbc3fd4de0b3b0d9e00e64d5434722936e68604e replaces message-text classification with typed conflict failures (stable diagnostic code, 409 preserved for superseded previews), rebases on latest main, and rebuilds the chat bundle from merged sources; 95 Python cases, 7 TypeScript cadence cases, typecheck, Ruff, mypy, docs governance and the PWA bundle smoke passed locally, and the new wording-independent regression fails on the pre-repair code.

@huangruiteng
huangruiteng merged commit e67483f into main Sep 24, 2026
8 checks passed
@huangruiteng
huangruiteng deleted the codex/automation-cadence-settings branch September 24, 2026 05:04
@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Self-repair and merged decision record (admin-bypass merge, maintainer-authorized).

  • Requested change addressed: the prior review on c350b60b64689438ddf51b3ac40e5e929d7b9093 blocked the settings API because it classified 409 / automation_cadence_conflict by looking for revision conflict or stale inside the error text, while the quota owner raised a generic request_rejected for a stale expected_revision. Rewording the message would have turned a recoverable refresh-retry conflict into a 400. The failure classification is now type-driven.
  • Exact head reviewed and merged: bbc3fd4de0b3b0d9e00e64d5434722936e68604e; merge commit on main: e67483f3304599db2cb679718764dfe11dda73de.
  • Before the merge, loopx pr-review --check-merge-readiness 4933@bbc3fd4d... returned ready=true with no blocking reasons, using the author-owned exact-head review published above.
  • git diff bbc3fd4d... e67483f33 is empty: the merged main tree is byte-identical to the head that was validated.

Repair content:

  • loopx/control_plane/quota/automation_cadence.ts now raises a typed conflict (EffectRuntimeConflictError, code=automation_cadence_revision_conflict) when the persisted revision no longer matches expected_revision, instead of a generic request_rejected.
  • loopx/chat_automation_cadence_api.py maps failures by exception type: typed conflicts (including a superseded preview digest) become 409 + automation_cadence_conflict, rejected input stays 400 + invalid_automation_cadence_request. No error text participates in the status decision.
  • Regressions: test_chat_cadence_conflict_status_ignores_message_wording monkeypatches the runtime result to a typed conflict whose message is "policy advanced" and asserts 409; the stale-preview end-to-end case asserts 409; tests/control_plane_ts/automation_cadence.test.ts asserts kind === "conflict" with the stable code. The new Python regression fails on the pre-repair code (assert 400 == 409, reproduced).
  • Latest main was merged; conflicts were the generated chat bundle (rename/rename) and the RFC appendices. Resolution: keep both RFC narratives (main's two-phase start/admission recovery text plus this branch's M3 settings milestone), then rebuild the chat bundle from the merged sources with npm run build:chat (vite.config.chat.config.ts), leaving asset-retention.json at two generations with superseded assets pruned.

Validation at the merged head:

  • Passed: 95 Python cases across chat automation cadence API, chat Goal configuration API, chat machine configuration API, automation cadence and the Turn executor; 7 TypeScript cadence cases; npm run typecheck:control-plane; configured Ruff; mypy (23 modules); examples/docs-governance-smoke.py; examples/dashboard-pwa-bundle-smoke.py; chat bundle rebuild via vite build:chat.
  • Skipped locally: the Playwright browser smoke (browser dependency unavailable in this run).
  • Not awaited: remote CI, per this Goal's wait_for_ci=false and the maintainer's explicit instruction for this run. CI on the previous head was green apart from the advisory node-forward-compatibility lane (continue-on-error: true), which is frequently cancelled on its time budget.
  • Change-quality receipt: cqr_e94d90f34376d16bbbb4 (decision=pass).
  • Carried limits: the Codex App timer-to-pre-start-hook admission path is still unqualified, and this settings milestone is a candidate deliverable rather than a promoted default; the settings UI interaction itself was not changed here, so the typed-conflict contract depends on the quota owner continuing to raise typed conflicts rather than prose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant